package snooker.scoreboard;

import java.util.LinkedList;
import java.util.List;

public class Statistics {

	public static List<StatisticRecord> getMatchStatistics(Match match) {
		
		// Statistics values
		int wins1 = match.getPlayer1FrameWins();
		int wins2 = match.getPlayer2FrameWins();
		int highestScore1 = 0;
		int highestScore2 = 0;
		int sumScore1 = 0;
		int sumScore2 = 0;
		double avgScore1 = 0;
		double avgScore2 = 0;
		
		int sumBalls1 = 0;
		int sumBalls2 = 0;
		int maxBalls1 = 0;
		int maxBalls2 = 0;
		double avgBalls1 = 0;
		double avgBalls2 = 0;
		
		int maxBallsInBreak1 = 0;
		int maxBallsInBreak2 = 0;
		int highestBreak1 = 0;
		int highestBreak2 = 0;
		double avgBreak1 = 0;
		double avgBreak2 = 0;
		
		int sumBreak1 = 0;
		int sumBreak2 = 0;
		int breakCount1 = 0;
		int breakCount2 = 0;
		double avgBallsInBreak1 = 0;
		double avgBallsInBreak2 = 0;
		
		int sumFouls1 = 0;
		int sumFouls2 = 0;
		int maxFoul1 = 0;
		int maxFoul2 = 0;
		int minFoul1 = 0;
		int minFoul2 = 0;
		double avgFoul1 = 0;
		double avgFoul2 = 0;
		
		int sumFoulScore1 = 0;
		int sumFoulScore2 = 0;
		int maxFoulScore1 = 0;
		int maxFoulScore2 = 0;
		int minFoulScore1 = 0;
		int minFoulScore2 = 0;
		double avgFoulScore1 = 0;
		double avgFoulScore2 = 0;
		
		long sumTime = 0;
		long maxTime = 0;
		long minTime = 0;
		double avgTime = 0;
		
		// Loop through the frames
		boolean firstFrame = true;
		List<Frame> fl = match.getFrames();
		for (Frame f: fl) {
			
			// Local variables
			int score1 = 0;
			int score2 = 0;
			
			int ballsInFrame1 = 0;
			int ballsInFrame2 = 0;
			
			int foulBallsInFrame1 = 0;
			int foulBallsInFrame2 = 0;
			
			int foulScoresInFrame1 = 0;
			int foulScoresInFrame2 = 0;
			
			int lastBreakId1 = 0;
			int lastBreakId2 = 0;
			int breakBallsInFrame1 = 0;
			int breakBallsInFrame2 = 0;
			int breakScoreInFrame1 = 0;
			int breakScoreInFrame2 = 0;
			long lastFrameTime = 0;
			
			List<FrameState> history = f.getHistory().getHistory();
			
			//Time
			if (history.size() > 0) {
				lastFrameTime = history.get(history.size() - 1).getEventTime();
			}
			if (minTime == 0) {
				minTime = lastFrameTime;
			}
			if (lastFrameTime > maxTime) {
				maxTime = lastFrameTime; 
			}
			if (lastFrameTime < minTime) {
				minTime = lastFrameTime;
			}
			sumTime += lastFrameTime;
			
			// Loop through the frame history
			for (FrameState fs: history) {
				
				if (fs.getType().equals(Type.Pot) || 
						fs.getType().equals(Type.Freeball)) {
					if (fs.getPlayerId() == 1) {
						
						ballsInFrame1++;
						score1 += fs.getScore();
						
						breakBallsInFrame1++;
						breakScoreInFrame1 += fs.getScore();
						
						if (lastBreakId1 == 0 || lastBreakId1 != fs.getBreakId()) {
							breakCount1++;
							lastBreakId1 = fs.getBreakId();
							breakBallsInFrame1 = 1;
							breakScoreInFrame1 = fs.getScore();
						}
						if (breakBallsInFrame1 > maxBallsInBreak1) {
							maxBallsInBreak1 = breakBallsInFrame1;
						}
						if (breakScoreInFrame1 > highestBreak1) {
							highestBreak1 = breakScoreInFrame1;
						}
						sumBreak1 += fs.getScore();
					} else {
						ballsInFrame2++;
						score2 += fs.getScore();
						
						breakBallsInFrame2++;
						breakScoreInFrame2 += fs.getScore();
						
						if (lastBreakId2 == 0 || lastBreakId2 != fs.getBreakId()) {
							breakCount2++;
							lastBreakId2 = fs.getBreakId();
							breakBallsInFrame2 = 1;
							breakScoreInFrame2 = fs.getScore();
						}
						if (breakBallsInFrame2 > maxBallsInBreak2) {
							maxBallsInBreak2 = breakBallsInFrame2;
						}
						if (breakScoreInFrame2 > highestBreak2) {
							highestBreak2 = breakScoreInFrame2;
						}
						sumBreak2 += fs.getScore();
					}
				} else if (fs.getType().equals(Type.Foul)) {
					if (fs.getPlayerId() == 2) {
						
						score1 += fs.getScore();
						
						foulBallsInFrame2++;
						foulScoresInFrame2 += fs.getScore();
						
					} else {
						
						score2 += fs.getScore();
						
						foulBallsInFrame1++;
						foulScoresInFrame1 += fs.getScore();
						
					}
				}
			}
			
			// Score
			sumScore1 += score1;
			sumScore2 += score2;
			
			if (score1 > highestScore1) {
				highestScore1 = score1;
			}
			if (score2 > highestScore2) {
				highestScore2 = score2;
			}
			
			// Balls
			sumBalls1 += ballsInFrame1;
			sumBalls2 += ballsInFrame2;
			
			if (ballsInFrame1 > maxBalls1) {
				maxBalls1 = ballsInFrame1;
			}
			if (ballsInFrame2 > maxBalls2) {
				maxBalls2 = ballsInFrame2;
			}
			
			// Foul Balls
			sumFouls1 += foulBallsInFrame1;
			sumFouls2 += foulBallsInFrame2;
			
			if (foulBallsInFrame1 > maxFoul1) {
				maxFoul1 = foulBallsInFrame1;
			}
			if (foulBallsInFrame2 > maxFoul2) {
				maxFoul2 = foulBallsInFrame2;
			}
			
			// Foul Scores
			sumFoulScore1 += foulScoresInFrame1;
			sumFoulScore2 += foulScoresInFrame2;
			
			if (foulScoresInFrame1 > maxFoulScore1) {
				maxFoulScore1 = foulScoresInFrame1;
			}
			if (foulScoresInFrame2 > maxFoulScore2) {
				maxFoulScore2 = foulScoresInFrame2;
			}
			if (firstFrame) {
				minFoul1 = foulBallsInFrame1;
				minFoul2 = foulBallsInFrame2;
				minFoulScore1 = foulScoresInFrame1;
				minFoulScore2 = foulScoresInFrame2;
			} else {
				if (foulBallsInFrame1 < minFoul1) {
					minFoul1 = foulBallsInFrame1;
				}
				if (foulBallsInFrame2 < minFoul2) {
					minFoul2 = foulBallsInFrame2;
				}
				if (foulScoresInFrame1 < minFoulScore1) {
					minFoulScore1 = foulScoresInFrame1;
				}
				if (foulScoresInFrame2 < minFoulScore2) {
					minFoulScore2 = foulScoresInFrame2;
				}
			}
			firstFrame = false;
		}
		
		if (fl.size() > 0) {
			avgScore1 = sumScore1 / (double)fl.size();
			avgScore2 = sumScore2 / (double)fl.size();
			
			avgBalls1 = sumBalls1 / (double)fl.size();
			avgBalls2 = sumBalls2 / (double)fl.size();
			
			avgFoul1 = sumFouls1 / (double)fl.size();
			avgFoul2 = sumFouls2 / (double)fl.size();
			
			avgFoulScore1 = sumFoulScore1 / (double)fl.size();
			avgFoulScore2 = sumFoulScore2 / (double)fl.size();
			
			avgTime = sumTime / (double)fl.size();
		}
		
		if (breakCount1 > 0) {
			avgBreak1 = sumBreak1 / (double)breakCount1;
			avgBallsInBreak1 = sumBalls1 / (double)breakCount1;
		}
		if (breakCount2 > 0) {
			avgBreak2 = sumBreak2 / (double)breakCount2;
			avgBallsInBreak2 = sumBalls2 / (double)breakCount2;
		}
		
		List<StatisticRecord> stats = new LinkedList<StatisticRecord>();

		stats.add(new StatisticRecord("", match.getPlayer1Name(), match.getPlayer2Name()));
		// Wins
		stats.add(new StatisticRecord("Wins", "", ""));
		stats.add(new StatisticRecord("Frame win", wins1, wins2));
		stats.add(new StatisticRecord("", "", ""));
		// Scores
		stats.add(new StatisticRecord("Scores", "", ""));
		stats.add(new StatisticRecord("Total Score", sumScore1, sumScore2));
		stats.add(new StatisticRecord("Highest Score", highestScore1, highestScore2));
		stats.add(new StatisticRecord("Avarage Score", avgScore1, avgScore2));
		stats.add(new StatisticRecord("", "", ""));
		// Balls
		stats.add(new StatisticRecord("Balls", "", ""));
		stats.add(new StatisticRecord("Total Balls", sumBalls1, sumBalls2));
		stats.add(new StatisticRecord("Maximum Balls", maxBalls1, maxBalls2));
		stats.add(new StatisticRecord("Avarage Balls", avgBalls1, avgBalls2));
		stats.add(new StatisticRecord("", "", ""));
		// Breaks
		stats.add(new StatisticRecord("Breaks", "", ""));
		stats.add(new StatisticRecord("Max Balls in a Break", maxBallsInBreak1, maxBallsInBreak2));
		stats.add(new StatisticRecord("Avg. Balls in a Break", avgBallsInBreak1, avgBallsInBreak2));
		stats.add(new StatisticRecord("Highest Break", highestBreak1, highestBreak2));
		stats.add(new StatisticRecord("Avarage Break", avgBreak1, avgBreak2));
		stats.add(new StatisticRecord("", "", ""));
		// Fouls
		stats.add(new StatisticRecord("Fouls", "", ""));
		stats.add(new StatisticRecord("Number of Fouls", sumFouls1, sumFouls2));
		stats.add(new StatisticRecord("Min Foul", minFoul1, minFoul2));
		stats.add(new StatisticRecord("Max Foul", maxFoul1, maxFoul2));
		stats.add(new StatisticRecord("Avarage Fouls", avgFoul1, avgFoul2));
		stats.add(new StatisticRecord("", "", ""));
		stats.add(new StatisticRecord("Foul Scores", sumFoulScore1, sumFoulScore2));
		stats.add(new StatisticRecord("Min Foul Score", minFoulScore1, minFoulScore2));
		stats.add(new StatisticRecord("Max Foul Score", maxFoulScore1, maxFoulScore2));
		stats.add(new StatisticRecord("Avg. Foul Score", avgFoulScore1, avgFoulScore2));
		stats.add(new StatisticRecord("", "", ""));
		// Player performance difference
		double[] stat = getStat(new double[][]{
				{wins1, wins2},
				{sumScore1, sumScore2},
				{sumBalls1, sumBalls2}, 
				{avgBreak1, avgBreak2},
				{sumFouls2, sumFouls1},
				{sumFoulScore2, sumFoulScore1}});
		String player1Stat = Utils.formatDouble(stat[0]*100) + "%";
		String player2Stat =Utils.formatDouble(stat[1]*100) + "%";
		stats.add(new StatisticRecord("Performance", player1Stat, player2Stat));
		stats.add(new StatisticRecord("", "", ""));
		
		// Time
		stats.add(new StatisticRecord("Time", "", ""));
		stats.add(new StatisticRecord("Max Time", Utils.getTimeString(maxTime), ""));
		stats.add(new StatisticRecord("Min Time", Utils.getTimeString(minTime), ""));
		stats.add(new StatisticRecord("Avarage Time", Utils.getTimeString((long)avgTime), ""));
		return stats;
	}
	
	private static double[] getStat(double[][] valuePairs) {
		double player1StatSum = 0;
		double player2StatSum = 0;
		int statCount = 0;
		double sum = 0;
		
		for (double[] a: valuePairs) {
			if (a.length == 2) {
				statCount++;
				if (a[0] == 0 && a[1] == 0) {
					player1StatSum += 0.5;
					player2StatSum += 0.5;
				} else {
					sum = a[0] + a[1];
					player1StatSum += a[0]/sum;
					player2StatSum += a[1]/sum;
				}
			}
		}
		
		double player1P = 0.5;
		double player2P = 0.5;
		
		if (statCount > 0) {
			player1P = player1StatSum/statCount;
			player2P = player2StatSum/statCount;
		}
		return new double[]{player1P,player2P};
	}
	
	public static List<StatisticRecord> getFrameStatistics(Match match, int frameId) {
		
		// Statistics values
		int highestScore1 = 0;
		int highestScore2 = 0;
		int sumScore1 = 0;
		int sumScore2 = 0;
		
		int sumBalls1 = 0;
		int sumBalls2 = 0;
		int maxBalls1 = 0;
		int maxBalls2 = 0;
		
		int maxBallsInBreak1 = 0;
		int maxBallsInBreak2 = 0;
		int highestBreak1 = 0;
		int highestBreak2 = 0;
		double avgBreak1 = 0;
		double avgBreak2 = 0;
		
		int sumBreak1 = 0;
		int sumBreak2 = 0;
		int breakCount1 = 0;
		int breakCount2 = 0;
		
		int sumFouls1 = 0;
		int sumFouls2 = 0;
		int maxFoul1 = 0;
		int maxFoul2 = 0;
		
		int sumFoulScore1 = 0;
		int sumFoulScore2 = 0;
		int maxFoulScore1 = 0;
		int maxFoulScore2 = 0;
		
		long maxTime = 0;
		
		int sumLuckyBalls1 = 0;
		int sumLuckyBalls2 = 0;
		int sumLuckyScores1 = 0;
		int sumLuckyScores2 = 0;
		double luckyBallsPercent1 = 0;
		double luckyBallsPercent2 = 0;
		double luckyScoresPercent1 = 0;
		double luckyScoresPercent2 = 0;
		
		// Loop through the frames
		List<Frame> fl = match.getFrames();
		Frame f = null;
		if (frameId == -1) {
			f = match.getActFrame();
		} else {
			try {
				f = fl.get(frameId);
			} catch (IndexOutOfBoundsException e) {
				return null;
			}
		}
		// Local variables
		int score1 = 0;
		int score2 = 0;
		
		int ballsInFrame1 = 0;
		int ballsInFrame2 = 0;
		
		int foulBallsInFrame1 = 0;
		int foulBallsInFrame2 = 0;
		
		int foulScoresInFrame1 = 0;
		int foulScoresInFrame2 = 0;
		
		int lastBreakId1 = 0;
		int lastBreakId2 = 0;
		int breakBallsInFrame1 = 0;
		int breakBallsInFrame2 = 0;
		int breakScoreInFrame1 = 0;
		int breakScoreInFrame2 = 0;
		
		List<FrameState> history = f.getHistory().getHistory();
		
		//Time
		if (history.size() > 0) {
			maxTime = history.get(history.size() - 1).getEventTime();
		}
		
		// Loop through the frame history
		for (FrameState fs: history) {
			
			if (fs.getType().equals(Type.Pot) || 
					fs.getType().equals(Type.Freeball)) {
				if (fs.getPlayerId() == 1) {
					
					ballsInFrame1++;
					score1 += fs.getScore();
					
					breakBallsInFrame1++;
					breakScoreInFrame1 += fs.getScore();
					
					if (lastBreakId1 == 0 || lastBreakId1 != fs.getBreakId()) {
						breakCount1++;
						lastBreakId1 = fs.getBreakId();
						breakBallsInFrame1 = 1;
						breakScoreInFrame1 = fs.getScore();
					}
					if (breakBallsInFrame1 > maxBallsInBreak1) {
						maxBallsInBreak1 = breakBallsInFrame1;
					}
					if (breakScoreInFrame1 > highestBreak1) {
						highestBreak1 = breakScoreInFrame1;
					}
					sumBreak1 += fs.getScore();
					if (fs.isLuckyShot()) {
						sumLuckyBalls1++;
						sumLuckyScores1 += fs.getScore();
					}
				} else {
					ballsInFrame2++;
					score2 += fs.getScore();
					
					breakBallsInFrame2++;
					breakScoreInFrame2 += fs.getScore();
					
					if (lastBreakId2 == 0 || lastBreakId2 != fs.getBreakId()) {
						breakCount2++;
						lastBreakId2 = fs.getBreakId();
						breakBallsInFrame2 = 1;
						breakScoreInFrame2 = fs.getScore();
					}
					if (breakBallsInFrame2 > maxBallsInBreak2) {
						maxBallsInBreak2 = breakBallsInFrame2;
					}
					if (breakScoreInFrame2 > highestBreak2) {
						highestBreak2 = breakScoreInFrame2;
					}
					sumBreak2 += fs.getScore();
					if (fs.isLuckyShot()) {
						sumLuckyBalls2++;
						sumLuckyScores2 += fs.getScore();
					}
				}
			} else if (fs.getType().equals(Type.Foul)) {
				if (fs.getPlayerId() == 2) {
					
					score1 += fs.getScore();
					
					foulBallsInFrame2++;
					foulScoresInFrame2 += fs.getScore();
					
				} else {
					
					score2 += fs.getScore();
					
					foulBallsInFrame1++;
					foulScoresInFrame1 += fs.getScore();
					
				}
			}
		}
		
		// Score
		sumScore1 += score1;
		sumScore2 += score2;
		
		if (score1 > highestScore1) {
			highestScore1 = score1;
		}
		if (score2 > highestScore2) {
			highestScore2 = score2;
		}
		
		// Balls
		sumBalls1 += ballsInFrame1;
		sumBalls2 += ballsInFrame2;
		
		if (ballsInFrame1 > maxBalls1) {
			maxBalls1 = ballsInFrame1;
		}
		if (ballsInFrame2 > maxBalls2) {
			maxBalls2 = ballsInFrame2;
		}
		
		// Foul Balls
		sumFouls1 += foulBallsInFrame1;
		sumFouls2 += foulBallsInFrame2;
		
		if (foulBallsInFrame1 > maxFoul1) {
			maxFoul1 = foulBallsInFrame1;
		}
		if (foulBallsInFrame2 > maxFoul2) {
			maxFoul2 = foulBallsInFrame2;
		}
		
		// Foul Scores
		sumFoulScore1 += foulScoresInFrame1;
		sumFoulScore2 += foulScoresInFrame2;
		
		if (foulScoresInFrame1 > maxFoulScore1) {
			maxFoulScore1 = foulScoresInFrame1;
		}
		if (foulScoresInFrame2 > maxFoulScore2) {
			maxFoulScore2 = foulScoresInFrame2;
		}
		
		if (breakCount1 > 0) {
			avgBreak1 = sumBreak1 / (double)breakCount1;
		}
		if (breakCount2 > 0) {
			avgBreak2 = sumBreak2 / (double)breakCount2;
		}
		
		//Lucky factor:
		luckyScoresPercent1 = (sumLuckyScores1 / (double)sumScore1) * 100;
		luckyScoresPercent2 = (sumLuckyScores2 / (double)sumScore2) * 100;
		luckyBallsPercent1 = (sumLuckyBalls1 / (double)sumBalls1) * 100;
		luckyBallsPercent2 = (sumLuckyBalls2 / (double)sumBalls2) * 100;
		
		List<StatisticRecord> stats = new LinkedList<StatisticRecord>();

		stats.add(new StatisticRecord("", match.getPlayer1Name(), match.getPlayer2Name()));
		// Scores
		stats.add(new StatisticRecord("Scores", "", ""));
		stats.add(new StatisticRecord("Total Score", sumScore1, sumScore2));
		stats.add(new StatisticRecord("", "", ""));
		// Balls
		stats.add(new StatisticRecord("Balls", "", ""));
		stats.add(new StatisticRecord("Total Balls", sumBalls1, sumBalls2));
		stats.add(new StatisticRecord("", "", ""));
		// Breaks
		stats.add(new StatisticRecord("Breaks", "", ""));
		stats.add(new StatisticRecord("Max Balls in a Break", maxBallsInBreak1, maxBallsInBreak2));
		stats.add(new StatisticRecord("Highest Break", highestBreak1, highestBreak2));
		stats.add(new StatisticRecord("Avarage Break", avgBreak1, avgBreak2));
		stats.add(new StatisticRecord("", "", ""));
		// Fouls
		stats.add(new StatisticRecord("Fouls", "", ""));
		stats.add(new StatisticRecord("Number of Fouls", sumFouls1, sumFouls2));
		stats.add(new StatisticRecord("Foul Scores", sumFoulScore1, sumFoulScore2));
		stats.add(new StatisticRecord("", "", ""));
		// Lucky factor:
		stats.add(new StatisticRecord("Lucky factor", "", ""));
		stats.add(new StatisticRecord("Lucky Balls", sumLuckyBalls1 + " (" + Math.round(luckyBallsPercent1) + "%)", sumLuckyBalls2 + " (" + Math.round(luckyBallsPercent2) + "%)"));
		stats.add(new StatisticRecord("Lucky Scores", sumLuckyScores1 + " (" + Math.round(luckyScoresPercent1) + "%)", sumLuckyScores2 + " (" + Math.round(luckyScoresPercent2) + "%)"));
		stats.add(new StatisticRecord("", "", ""));
		//Player performace
		double[] stat = getStat(new double[][]{
				{sumScore1, sumScore2},
				{sumBalls1, sumBalls2}, 
				{avgBreak1, avgBreak2},
				{sumFouls2, sumFouls1},
				{sumFoulScore2, sumFoulScore1}});
		String player1Stat = Utils.formatDouble(stat[0]*100) + "%";
		String player2Stat =Utils.formatDouble(stat[1]*100) + "%";
		stats.add(new StatisticRecord("Performance", player1Stat, player2Stat));
		stats.add(new StatisticRecord("", "", ""));
		// Time
		stats.add(new StatisticRecord("Time", Utils.getTimeString(maxTime), ""));
		return stats;
	}
}
